﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.UI.HtmlControls;
using uk.ac.soton.ses.DAL;
using System.Web;
using uk.ac.soton.ses.WebControls;
using System.Security.Principal;
using uk.ac.soton.ses.UserControls;
using uk.ac.soton.ses.layouts;

namespace uk.ac.soton.ses.layouts
{
    public partial class ParameterGraphCategory : LayoutsPageBase
    {
        private List<string> requestedParameters = null;
        private List<int> requestedExperimentIDs = null;

        private bool IsInitialised = false;
        Dictionary<int, Experiment> expDict = null;
        MaterialsMetadataContext dbcontext = null;
        private List<string> commonParameters = null;
        private ExperimentQuickSearch _searchControl = null;

        private List<int> excludedExperiments = null;
        private List<string> excludedParameters = null;

        private bool stackedGraph = false;
        private string graphType = "";
        private bool useParentParamAsDataset;

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            this.dbcontext = HttpContext.Current.GetMaterialsDBContext();

            //Find the experiments quick search box, cast it, and store it in a field
            this._searchControl = this.ctl_ExperimentQuickSearch as ExperimentQuickSearch;

            //LoadData

            this.ConfigureEvents();

            bool success = this.ProcessQueryString();

            if(success)
            {

                //Get a list of experiments the user has access to
                this.LoadExperiments();

                //Get a list of parameters that are common to the requested experiments
                this.FindParametersCommonToRequestedExperiments();

                //If the user's requested a parameter not available, remove it from the requested list
                this.ExcludeRequestedParametersNotAvailable();

                //Get a list of experiments that contain the parameters selected
                this.FindExperimentsWithRequestedParameters();

                //If the user does not have access to requested experiments, remove it from the requested list
                this.ExcludeRequestedExperimentsNotAvailable();

                //Let OnLoad do its stuff
                this.IsInitialised = true;
            }
            else
            {
                this.IsInitialised = false;
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            if (this.IsInitialised)
            {
                this.CreateGraph();

                if (!this.IsPostBack)
                {
                    this.lst_GraphType.SelectedValue = this.graphType;
                    this.chk_StackedGraph.Checked = this.stackedGraph;
                    this.chk_UseParentParamAsDataset.Checked = this.useParentParamAsDataset;
                    this.PopulateDatasetList();
                    this.PopulateCommonParametersList();
                    this.ReportExcludedItems();
                }
            }
        }

        private void ConfigureEvents()
        {
            //Events
            this.lst_Datasets.SelectedIndexChanged += new EventHandler(lst_Datasets_SelectedIndexChanged);
            this.lst_Parameters.SelectedIndexChanged += new EventHandler(lst_Parameters_SelectedIndexChanged);
            this._searchControl.ExperimentSelected += new ExperimentSelectedEventHandler(this.QuickSearchControl_ExperimentSelected);
            this.lst_GraphType.SelectedIndexChanged += new EventHandler(lst_GraphType_SelectedIndexChanged);
            this.chk_StackedGraph.CheckedChanged += new EventHandler(chk_StackedGraph_CheckedChanged);
            this.chk_UseParentParamAsDataset.CheckedChanged += new EventHandler(chk_UseParentParamAsDataset_CheckedChanged);
        }

        private bool ProcessQueryString()
        {
            //Get parameters
            string[] experimentIDStrings = Page.Request.QueryString.GetValues("ExperimentID") ?? new string[0];
            this.requestedParameters = (Page.Request.QueryString.GetValues("ParameterName") ?? new string[0]).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
            string graphTypeStr = Page.Request.QueryString["GraphType"] ?? "bar";
            string stackedGraphStr = Page.Request.QueryString["StackedGraph"] ?? "";
            string useParentParamStr = Page.Request.QueryString["UseParentParam"] ?? "";
            this.useParentParamAsDataset = (!string.IsNullOrEmpty(useParentParamStr) && useParentParamStr.Equals("true", StringComparison.OrdinalIgnoreCase));
            this.stackedGraph = (!string.IsNullOrEmpty(stackedGraphStr) && stackedGraphStr.Equals("true", StringComparison.OrdinalIgnoreCase));

            //Work out graph type
            if (graphTypeStr.Equals("bar", StringComparison.OrdinalIgnoreCase))
            {
                this.graphType = "bar";
            }
            else if (graphTypeStr.Equals("column", StringComparison.OrdinalIgnoreCase))
            {
                this.graphType = "column";
            }
            else if (graphTypeStr.Equals("pie", StringComparison.OrdinalIgnoreCase))
            {
                this.graphType = "pie";
            }
            else
            {
                this.graphType = "bar";
            }

            //Parse experiment IDs
            try
            {
                this.requestedExperimentIDs = experimentIDStrings.Select(id => int.Parse(id)).ToList();
            }
            catch (Exception ex)
            {
                if (ex is FormatException || ex is OverflowException || ex is ArgumentNullException)
                {
                    HDCSharedWebFunctions.AddErrorLabel("Could not parse experiment ID", this.PlaceHolder_Messages);
                    return false;
                }
                else
                {
                    throw;
                }
            }

            return true;
        }

        private void ReportExcludedItems()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder("Excluded experiments: ");
            sb.Append(string.Join(",", this.excludedExperiments.Select(id=>id.ToString()).ToArray()));
            sb.Append(".");
            this.lbl_ExcludedExperiments.Text = sb.ToString();

            sb = new System.Text.StringBuilder("Excluded parameters:");
            sb.Append(string.Join(",", this.excludedParameters.ToArray()));
            sb.Append(".");
            this.lbl_ExcludedParameters.Text = sb.ToString();
        }

        private void lst_Datasets_SelectedIndexChanged(object sender, EventArgs e)
        {
            List<int> selectedExperiments = new List<int>();
            foreach (ListItem li in this.lst_Datasets.Items)
            {
                if (li.Selected)
                {
                    try
                    {
                        int id = int.Parse(li.Value);
                        selectedExperiments.Add(id);
                    }
                    catch (Exception ex)
                    {
                        if (ex is FormatException || ex is OverflowException || ex is ArgumentNullException)
                        {
                            HDCSharedWebFunctions.AddErrorLabel("Experiment ID not correct format", this.PlaceHolder_Messages);
                            return;
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
            }

            this.requestedExperimentIDs = selectedExperiments;

            this.RedirectToNewUrl();
        }

        private void lst_Parameters_SelectedIndexChanged(object sender, EventArgs e)
        {
            List<string> selectedParameters = new List<string>();
            foreach (ListItem li in this.lst_Parameters.Items)
            {
                if (li.Selected)
                {
                    selectedParameters.Add(li.Value);
                }
            }

            this.requestedParameters = selectedParameters;

            this.RedirectToNewUrl();
        }

        private void chk_StackedGraph_CheckedChanged(object sender, EventArgs e)
        {
            this.stackedGraph = this.chk_StackedGraph.Checked;
            this.RedirectToNewUrl();
        }

        private void lst_GraphType_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.graphType = this.lst_GraphType.SelectedValue;
            this.RedirectToNewUrl();
        }

        private void chk_UseParentParamAsDataset_CheckedChanged(object sender, EventArgs e)
        {
            this.useParentParamAsDataset = this.chk_UseParentParamAsDataset.Checked;
            this.RedirectToNewUrl();
        }

        private void QuickSearchControl_ExperimentSelected(object sender, ExperimentSelectedEventArgs e)
        {
            this.requestedExperimentIDs.Add(e.SelectedExperimentID);
            this.RedirectToNewUrl();
        }

        private void CreateGraph()
        {
            //Create graph data in the following format:
            //categories = dataset
            //series = parameter name

            try
            {
                //Generate the graph data
                GraphData_Category graphData = this.GetDataForGraph();

                //At this point, we have a graph type, categories, series data and possibly plot options an additional function string. Generate the javascript.
                string js = graphData.FormattedJavaScript;

                //Add script to page
                string ctxkey = "highchart" + this.Page.GetHashCode().ToString("x");
                if (!this.ClientScript.IsClientScriptBlockRegistered(this.GetType(), ctxkey))
                {
                    //We don't want to have the CSS cached so we append the date to the URL
                    this.ClientScript.RegisterClientScriptBlock(this.GetType(), ctxkey, js, false);
                }
            }
            catch (Exception e)
            {
                HDCSharedWebFunctions.AddErrorLabel("Error creating graph: " + e.Message, this.PlaceHolder_Messages);
            }
        }


        private void LoadExperiments()
        {
            //Lookup current user
            WindowsIdentity identity = Context.User.Identity as WindowsIdentity;

            //Get the user's experiments
            IEnumerable<Experiment> usersExperiments = null;

            //A WMI Query might be required which needs elevated priveleges
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                usersExperiments = this.dbcontext.GetExperimentsForUser(identity.User, HDCExperimentAccessLevels.ReadOnly);
            });

            this.expDict = usersExperiments.ToDictionary(exp=>exp.ID);
        }

        private void PopulateDatasetList()
        {
            //Clear existing items
            this.lst_Datasets.Items.Clear();


            //Create ListItems for list
            List<ListItem> listItems = new List<ListItem>();
            foreach (Experiment exp in this.expDict.Values)
            {
                ListItem newListItem = new ListItem(exp.Name, exp.ID.ToString());
                newListItem.Selected = this.requestedExperimentIDs.Contains(exp.ID);
                listItems.Add(newListItem);
            }

            //Populate list
            this.lst_Datasets.Items.AddRange(
                //Sort it
                listItems.OrderBy(li => li.Text)
                //Convert it to an array
                .ToArray()
                );
        }

        private void ExcludeRequestedExperimentsNotAvailable()
        {
            this.excludedExperiments = new List<int>();

            //Just check the user can access the requested experiment (remove the ones not in the user's experiment list)
            int requestedExpsCount = this.requestedExperimentIDs.Count;
            for (int i = 0; i < requestedExpsCount; i++)
            {
                int reqExpID = this.requestedExperimentIDs[i];
                if (!this.expDict.ContainsKey(reqExpID))
                {
                    int removed = this.requestedExperimentIDs.RemoveAll(expid => expid == reqExpID);
                    this.excludedExperiments.Add(reqExpID);
                    requestedExpsCount -= removed;
                    i--;
                }
            }
        }

        private void ExcludeRequestedParametersNotAvailable()
        {
            this.excludedParameters = new List<string>();

            int requestedParamCount = this.requestedParameters.Count;
            for (int i = 0; i < requestedParamCount; i++)
            {
                string reqParam = this.requestedParameters[i];
                if (!this.commonParameters.Contains(reqParam))
                {
                    int removed = this.requestedParameters.RemoveAll(param => param == reqParam);
                    this.excludedParameters.Add(reqParam);
                    requestedParamCount -= removed;
                    i--;
                }
            }
        }

        private void PopulateCommonParametersList()
        {
            //Clear existing items
            this.lst_Parameters.Items.Clear();

            //Create ListItems for list
            List<ListItem> newListItems = new List<ListItem>();
            foreach (string param in this.commonParameters)
            {
                //The value of the list item is actually the same as the text (because we are not bothered about parameter ID in this case)
                ListItem newListItem = new ListItem(param, param);
                newListItem.Selected = this.requestedParameters.Contains(param);
                newListItems.Add(newListItem);
            }

            //Populate list
            this.lst_Parameters.Items.AddRange(
                //Sort it
                newListItems.OrderBy(li => li.Text)
                //Convert it to an array
                .ToArray()
                );
        }

        private void FindParametersCommonToRequestedExperiments()
        {
            //Only load common parameters if an experiment has been requested
            if (this.requestedExperimentIDs.Count > 0)
            {
                //Get the first experiment and load its parameters
                int expID = this.requestedExperimentIDs[0];
                Experiment exp = this.expDict[expID];
                exp.ExperimentParameters.Load(System.Data.Objects.MergeOption.OverwriteChanges);

                //Get a list of parameters, discounting duplicates
                List<string> newCommonParameters = exp.ExperimentParameters.Select(p => p.Name).Distinct(StringComparer.OrdinalIgnoreCase).ToList();

                //Iterate around the rest of the requested experiments', checking their parameters for common parameters
                for (int i = 1; i < this.requestedExperimentIDs.Count; i++)
                {
                    //Get the next experiment and load its parameters
                    expID = this.requestedExperimentIDs[i];
                    exp = this.expDict[expID];
                    exp.ExperimentParameters.Load(System.Data.Objects.MergeOption.OverwriteChanges);

                    //Get a list of parameter names from the next experiment
                    IEnumerable<string> expParamNames = exp.ExperimentParameters.Select(p => p.Name).Distinct(StringComparer.OrdinalIgnoreCase);

                    //Iterate around each of the parameters stored as common and ensure it is a parameter in this experiment
                    int paramcount = newCommonParameters.Count;
                    for (int j = 0; j < paramcount; j++)
                    {
                        string param = newCommonParameters[j];
                        if (expParamNames.Count(p => p.Equals(param, StringComparison.OrdinalIgnoreCase)) == 0)
                        {
                            //If the parameter does not exist in the list of common parameters, remove it from common parameters
                            int removed = newCommonParameters.RemoveAll(p => p.Equals(param, StringComparison.OrdinalIgnoreCase));

                            //We've removed some parameters, adjust our boundary for the for loop
                            paramcount -= removed;

                            //We've just removed the parameter we are on so adjust the for loop counter so we redo the new parameter in this slot
                            j--;
                        }
                    }
                }

                //Overwrite the global common parameters variable
                this.commonParameters = newCommonParameters;
            }
            else
            {
                //Set it to a blank list
                this.commonParameters = new List<string>();
            }
        }

        private void FindExperimentsWithRequestedParameters()
        {
            if (this.requestedParameters.Count > 0)
            {
                Dictionary<int, Experiment> newDict = new Dictionary<int, Experiment>();
                foreach (Experiment exp in this.expDict.Values)
                {
                    if (!exp.ExperimentParameters.IsLoaded)
                    {
                        exp.ExperimentParameters.Load();
                    }

                    bool allParamsPresent = true;

                    List<string> expParamNames = exp.ExperimentParameters.Select(param => param.Name).ToList();

                    foreach (string reqParam in this.requestedParameters)
                    {
                        if (!expParamNames.Contains(reqParam, StringComparer.OrdinalIgnoreCase))
                        {
                            allParamsPresent = false;
                            break;
                        }
                    }

                    if (allParamsPresent)
                    {
                        newDict.Add(exp.ID, exp);
                    }
                }

                this.expDict = newDict;
            }
        }

        private void RedirectToNewUrl()
        {
            string experimentids = (this.requestedExperimentIDs.Count > 0 ? "&" + string.Join("&", this.requestedExperimentIDs.Select(id => string.Format("ExperimentID={0}", id)).ToArray()) : "");
            string parameters = (this.requestedParameters.Count > 0 ? "&" + string.Join("&", this.requestedParameters.Select(p => string.Format("ParameterName={0}", p)).ToArray()) : "");
            string graphTypeStr = "&GraphType=" + this.graphType;
            string stackedGraphStr = "&StackedGraph=" + this.stackedGraph.ToString().ToLower();
            string useParentParamStr = "&UseParentParam=" + this.useParentParamAsDataset.ToString().ToLower();
            string redirectUrl = string.Format("{0}?{1}{2}{3}{4}{5}", this.Page.Request.Url.GetLeftPart(UriPartial.Path), experimentids, parameters, graphTypeStr, stackedGraphStr, useParentParamStr);
            this.Response.Redirect(redirectUrl);
        }


        private GraphData_Category GetDataForGraph()
        {
            Dictionary<string, Dictionary<string, string>> graphDataDictionary = new Dictionary<string, Dictionary<string, string>>();

            //Read the parameters' data and generate a dictionary that will be used for creating the graph
            foreach (string reqparam in this.requestedParameters)
            {
                //Check again that it is in our common parameters, otherwise ignore it
                if (!this.commonParameters.Exists(s => s.Equals(reqparam, StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }

                foreach (int reqexpid in this.requestedExperimentIDs)
                {
                    Experiment exp = this.expDict[reqexpid];
                    ExperimentParameter[] exp_params = exp.LookupParameters(reqparam);

                    //Sometimes we get more than one matching parameter. In this case treat it as a different dataset and append the count to the name.
                    bool multipleParamsInDS = (exp_params.Count() > 1);
                    int currentparam = 1;

                    foreach (ExperimentParameter param in exp_params)
                    {
                        //Generate a name for this dataset
                        string dataset_name = string.Format("{0} ({1})", exp.Name, exp.ID);

                        string append_str = "";

                        if (this.useParentParamAsDataset)
                        {
                            ExperimentParameter parentParam = this.dbcontext.GetParentParameter(param);
                            if (parentParam != null)
                            {
                                append_str = string.Format("_{0} ({1})", parentParam.Name, parentParam.ID);
                            }
                        }

                        if (multipleParamsInDS && string.IsNullOrEmpty(append_str))
                        {
                            append_str = "_" + currentparam.ToString();
                        }

                        dataset_name += append_str;

                        //Check we can read it and there is a number, otherwise don't add it
                        double paramValue;
                        if (double.TryParse(param.Value, out paramValue))
                        {
                            //Create or retrieve the dataset
                            if (!graphDataDictionary.ContainsKey(dataset_name))
                            {
                                graphDataDictionary.Add(dataset_name, new Dictionary<string, string>());
                            }

                            Dictionary<string, string> thisdataset = graphDataDictionary[dataset_name];
                            thisdataset[reqparam] = param.Value;
                        }

                        currentparam++;
                    }
                }
            }

            GraphData_Category graphData = new GraphData_Category(graphDataDictionary, this.graphType, this.stackedGraph, this.requestedParameters);

            return graphData;
        }


    }
}
